﻿#region License
// Copyright 2009 Buu Nguyen (http://www.buunguyen.net/blog)
// 
// Licensed under the Apache License, Version 2.0 (the "License"); 
// you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at 
// 
// http://www.apache.org/licenses/LICENSE-2.0 
// 
// Unless required by applicable law or agreed to in writing, software 
// distributed under the License is distributed on an "AS IS" BASIS, 
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
// See the License for the specific language governing permissions and 
// limitations under the License.
// 
// The latest version of this file can be found at http://dotcommon.codeplex.com
#endregion

using System;
using System.Collections.Generic;
using System.Net;
using System.Web;
using System.Web.Mvc;
using DotCommon.Exception;

namespace DotCommon.Mvc
{
    /// <summary>
    /// Filter to throw exception if a certain action or controller is visited more frequently 
    /// than the specified maximum visits per minute.
    /// </summary>
    public class RejectSpamAttribute : ActionFilterAttribute
    {
        private enum ActionType { Visit }
        private class Hit { public int Count { get; set; } }

        /// <summary>
        /// If number of visits per minute exceeds this, exception is thrown
        /// </summary>
        public int MaxVisitsPerMinute 
        { 
            set
            {
                thresholdMap[ActionType.Visit] = value;
            } 
        }

        private readonly IDictionary<ActionType, int> thresholdMap = 
            new Dictionary<ActionType, int>();

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (SpamSmelled(thresholdMap, ActionType.Visit))
            {
                filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                throw new DotCommonException("You're trying to access our site too frequently");
            }
        }

        private static bool SpamSmelled(IDictionary<ActionType, int> thresholdMap, 
            ActionType actionType)
        {
            var context = HttpContext.Current;
            if (context.Request.Browser.Crawler) return false;

            var key = actionType + GetUserIP(context);
            var hit = (Hit)(context.Cache[key] ?? new Hit());
            if (hit.Count > thresholdMap[actionType])
                return true;

            hit.Count++;
            if (hit.Count == 1)
                context.Cache.Add(key, hit, null, DateTime.Now.AddMinutes(1),
                                  System.Web.Caching.Cache.NoSlidingExpiration,
                                  System.Web.Caching.CacheItemPriority.Normal, null);
            return false;
        }

        /// <summary>
        /// See http://haacked.com/archive/2006/10/11/A_Gotcha_Identifying_the_Users_IP_Address.aspx
        /// </summary>
        private static string GetUserIP(HttpContext context)
        {
            var ipFromProxy = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
            return string.IsNullOrEmpty(ipFromProxy)
                       ? context.Request.UserHostAddress
                       : ipFromProxy;
        }
    }
}